package AutoTraderFraudCalculator;
import java.io.*;
import java.net.*;
import java.util.*;

/**
 * <code>AutoTraderFraudCalculator</code> is the class which links the classes
 * <code>Analysis</code> and <code>Listing</code> together in oder to become a
 * program that is able to access the <code>autotrader.ca</code> website and
 * retrieve all the listings of a particular make and model, and then analyze
 * the data in order to output a risk factor. This risk factor calculates the
 * percentage that a certain car ad is a scam or not. In addition, this program
 * is able to output the data used in an XML file for further analysis.
 * 
 */
public class AutoTraderFraudCalculator 
{
    private static Scanner pageReader;
    private static final String AUTO_TRADER = "http://www.autotrader.ca";
    private static int minPrice;
    private static int avgPrice;
    private static int maxPrice;
    private static int minMileage;
    private static int avgMileage;
    private static int maxMileage;
    
    /**
     * Initialize the <code>Scanner pageReader</code> to read the 
     * <code>url</code>.
     * 
     * @param url A <code>String</code> representation of the URL to read.
     */
    public void setupPageReader(String url)
    {
        try
        {
            URL linkToAutoTrader = new URL(url);
            pageReader = new Scanner(linkToAutoTrader.openStream());
        }
        catch(MalformedURLException e)
        {
            System.err.println(e.getMessage());
        }
        catch(IOException e)
        {
            System.err.println(e.getMessage());
        }
    }
    
    /**
     * Retrieve a list of makes from the <code>AUTO_TRADER</code> site.
     * 
     * @return An <code>ArrayList<String></code> of makes.
     */
    public ArrayList<String> getCarMakes()
    {
        boolean makesComing = false;
        boolean doneExtracting = false;
        List<String> makes = new ArrayList<String>();
        String lineText = "";
        String make = "";
        
        setupPageReader(AUTO_TRADER);
        while (pageReader.hasNext() && !doneExtracting)
        {
            lineText = pageReader.nextLine();
            
            if (lineText.contains
                    ("A huge array of makes to choose from in"))
            {
                makesComing = true;
            }
            else if (makesComing)
            {
                if (lineText.contains("&nbsp;"))
                {
                    make = lineText.split("&nbsp;|6\">")[1];
                    makes.add(make);
                }
            }
            
            if (lineText.contains("What model are you after?"))
            {
                makesComing = false;
                doneExtracting = true;
            }
        }
        
        pageReader.close();
        return (new ArrayList<String>(makes));
    }
    
    /**
     * Retrieve a map of models and the respective URLs for the models.
     * @param carMake The particular car make to search through.
     * @return A <code>TreeMap</code> representation with models as keys and 
     *         URLs as values.
     */
    public TreeMap<String, String> getCarModels(String carMake)
    {           
        Map<String, String> modelsToUrl = new TreeMap<String, String>();
        String lineText = "";
        String model = "";
        String modelUrl = "";
        String makeUrl = AUTO_TRADER + "/Ajax/Models.aspx?cat2=7%2c11%2c9%2c10&"
                            + "prv=Ontario&r=40&rprv=True&prx="
                            + "0&sts=Used&make=" + carMake;
        
        makeUrl = makeUrl.replaceAll("\\s+", "+");
        makeUrl = makeUrl.replace("&#39;", "'");
        setupPageReader(makeUrl);
        while (pageReader.hasNext())
        {
            lineText = pageReader.nextLine();
            
            if (lineText.contains("&nbsp;"))
            {
                model = lineText.split("&nbsp;|6\">")[1];
                modelUrl = lineText.split("\"")[5];
                modelUrl = modelUrl.replace("amp;", "");
                modelUrl = modelUrl.replaceAll("\\s+", "+");
                modelUrl = modelUrl.replace("&#39;", "'");
                modelsToUrl.put(model, modelUrl);
            }
        }
        
        pageReader.close();
        return (new TreeMap<String, String>(modelsToUrl));
    }
    
    /**
     * Download and access the car listings for a particular make and model, 
     * then store them in a list. Pass this list to <code>getCarSpecifications
     * </code> along with the car made and model.
     * 
     * @param carMake The name of a car make.
     * @param carModel The name of a car model.
     * @throws IOException 
     * @see #getCarSpecifications(java.util.ArrayList, java.lang.String, 
     *                            java.lang.String)
     */
    public void downloadCarListings(String carMake, String carModel) 
            throws IOException
    {
        CarListings carAds = new CarListings();
        List<String> uniqueUrls = new ArrayList<String>();
        Listing currentListing;
        int pageNum = 1;
        int infoPieces = 0;
        boolean adTitleComing = false;
        boolean sifted = false;
        boolean listingTypeComing = false;
        String lineText = "";
        String nextPageUrl = "";
        String adTitle = "";
        String adUrl = "";
        String listingType = "";
        
        setupPageReader(AUTO_TRADER + getCarModels(carMake).get(carModel));        
        while (pageReader.hasNext())
        {
            lineText = pageReader.nextLine();
            while (!lineText.contains("Priority Listings") && 
                    !lineText.contains("Search Results") && !sifted)
            {
                lineText = pageReader.nextLine();
            }
            
            sifted = true;
            
            if (adTitleComing && !uniqueUrls.contains(adUrl))
            {
                adTitleComing = false;
                adTitle = lineText.trim();
                adTitle = adTitle.replaceAll("-+", "");
                currentListing = new Listing();
                currentListing.setTitle(adTitle);
                currentListing.setUrl(adUrl);
                
                if (listingType.contentEquals(""))
                {
                    listingType = "Dealership";
                }
                
                currentListing.setSpecification
                                            ("Type of Listing:", listingType);
                
                uniqueUrls.add(adUrl);
                carAds.pushAd(currentListing);
                listingType = "";
            }
            else if (lineText.contains("class=\"carlink\">"))
            {
                adTitleComing = true;
                adUrl = AUTO_TRADER + lineText.split("\"")[1];
                adUrl = adUrl.replaceAll("\\s+", "+");
                adUrl = adUrl.replace("&#39;", "'");
            }
            else if (listingTypeComing)
            {
                if (lineText.trim().contentEquals("Private Listing"))
                {
                    listingType = "Private Listing";
                    listingTypeComing = false;
                }
            }
            else if (lineText.contains("pnlPrivate\">"))
            {
                listingTypeComing = true;
            }
            else if (listingTypeComing && lineText.contains("</div>"))
            {
                listingTypeComing = false;
            }
            else if (lineText.contains("\">Next &rsaquo;"))
            {
                pageReader.close();
                sifted = false;
                pageNum++;
                nextPageUrl = lineText.split("\">Next &rsaquo;")[0];
                infoPieces = nextPageUrl.split("\"").length;             
                nextPageUrl = nextPageUrl.split("\"")[infoPieces - 1];
                nextPageUrl = AUTO_TRADER + nextPageUrl.replace("amp;", "");
                nextPageUrl = nextPageUrl.replaceAll("\\s+", "+");
                nextPageUrl = nextPageUrl.replace("&#39;", "'");
                setupPageReader(nextPageUrl);
            }
        }
        
        pageReader.close();
        getCarSpecifications(new CarListings(carAds), carMake, carModel);
    }
    
    /**
     * Access each car ad in <code>carAds</code> and retrieve the car
     * specifications from the ad. Store the specifications in local instance
     * variables and call <code>writeToXml(carAds, carMake, carModel)</code>
     * with carAds including the retrieved specifications.
     * 
     * @param carAds a list of <code>Listings</code> that need specification
     *               information.
     * @param carMake The name of the car make.
     * @param carModel The name of the car model.
     * @throws IOException 
     * @see #writeToXml(java.util.ArrayList, java.lang.String, java.lang.String) 
     */
    public void getCarSpecifications(CarListings carAds, String carMake, 
            String carModel) throws IOException
    {
        boolean specsComing = false;
        boolean priceComing = false;
        boolean priceFound = false;
        boolean phoneNumComing = false;
        boolean phoneNumFound = false;
        boolean sifted = false;
        boolean doneExtracting = false;
        String specType = "";
        String specValue = "";
        String lineText = "";
        String nextUrl = "";
        String price = "";
        String mileage = "";
        String precPhoneNum = "";
        Analysis analyzation = new Analysis();
        
        for (int i = 0; i < carAds.getSize(); i++)
        {
            nextUrl = carAds.getAd(i).getUrl();
            setupPageReader(nextUrl);
            specsComing = false;
            priceComing = false;
            priceFound = false;
            phoneNumComing = false;
            phoneNumFound = false;
            sifted = false;
            doneExtracting = false;
            
            while (pageReader.hasNext() && !doneExtracting)
            {
                lineText = pageReader.nextLine();
                while (!lineText.contains
                                ("<!-- Detail info -->") && !sifted)
                {
                    lineText = pageReader.nextLine();
                }
                
                sifted = true;
                        
                if (specsComing && lineText.contains("<strong>"))
                {
                    specType = lineText.split("<strong>| </strong>|<br/>")[1];
                    specType = specType.replaceAll("-+", "");
                    
                    if (specType.contentEquals("Mileage:"))
                    {
                        specValue = "";
                        mileage = lineText.split
                                    ("<strong>| </strong>|<br/>")[2];
                        
                        for (int k = 0; k < mileage.length(); k++)
                        {
                            if (mileage.substring(k, k + 1).matches
                                                                ("[0-9 kms]"))
                            {
                                specValue += mileage.substring(k, k + 1);
                            }
                        }
                        
                        if (!specValue.substring(0, 1).matches("\\d"))
                        {
                            specValue = "N/A";
                        }
                    }
                    else
                    {
                        specValue = lineText.split
                                        ("<strong>| </strong>|<br/>")[2];
                        
                        specValue = specValue.replaceAll("-+", "");
                        if (specValue.contentEquals(""))
                        {
                            specValue = "N/A";
                        }
                    }
                    
                    carAds.getAd(i).setSpecification(specType, specValue);
                }
                else if (lineText.contains("class=\"d_title\">Technical"))
                {
                    specsComing = true;
                }
                else if (specsComing && lineText.contains("</table>"))
                {
                    specsComing = false;
                }
                else if (lineText.contains(">Price:"))
                {
                    priceComing = true;
                }
                else if (priceComing && lineText.contains("\">$"))
                {
                    priceComing = false;
                    priceFound = true;
                    specType = "Price:";
                    specValue = lineText.split("\\$|</span></td>")[1];
                                        
                    for (int k = 0; k < specValue.length(); k++)
                    {                        
                        if (!specValue.substring(k, k + 1).contains(","))
                        {
                            price += specValue.substring(k, k + 1);
                        }
                    }
                    
                    specValue = price;
                    
                    if (specValue.contains("."))
                    {
                        specValue = specValue.substring
                                                    (0, specValue.length() - 3);
                    }
                    
                    price = "";
                    carAds.getAd(i).setSpecification(specType, specValue);
                }
                else if (lineText.contains("<div class=\"contact_seller\">"))
                {
                    phoneNumComing = true;
                    
                    if (!priceFound)
                    {
                        specType = "Price:";
                        specValue = "N/A";
                        carAds.getAd(i).setSpecification(specType, specValue);
                    }
                }
                else if (!phoneNumFound && lineText.matches
                                    ("^.*\\d\\d\\d-\\d\\d\\d-\\d\\d\\d\\d.*$"))
                {
                    phoneNumFound = true;
                    specType = "Contact Number:";
                    precPhoneNum = lineText.split
                                        ("\\d\\d\\d-\\d\\d\\d-\\d\\d\\d\\d")[0];

                    specValue = lineText.substring(precPhoneNum.length(), 
                                                    precPhoneNum.length() + 12);
                    
                    carAds.getAd(i).setSpecification(specType, specValue);
                }
                else if (phoneNumComing && lineText.contains("</div>"))
                {
                    doneExtracting = true;
                    
                    if (!phoneNumFound)
                    {
                        specType = "Contact Number:";
                        specValue = "N/A";
                        carAds.getAd(i).setSpecification(specType, specValue);
                    }
                    
                    if (!carAds.getAd(i).getSpecs().containsKey("Mileage:"))
                    {
                        specType = "Mileage:";
                        specValue = "N/A";
                        carAds.getAd(i).setSpecification(specType, specValue);
                    }
                }
            }
            
            pageReader.close();
        }
        
        carAds = analyzation.initCalculations(new CarListings(carAds));
        minPrice = analyzation.getMinPrice();
        avgPrice = analyzation.getAvgPrice();
        maxPrice = analyzation.getMaxPrice();
        minMileage = analyzation.getMinMileage();
        avgMileage = analyzation.getAvgMileage();
        maxMileage = analyzation.getMaxMileage();
        writeToXml(carAds, carMake, carModel);
    }
    
    /**
     * Temporary method to write a pseudo XML file.
     * 
     * @param carAds The list of car ads with the specifications added.
     * @param carMake The car make that the ads came from.
     * @param carModel The car model that was being accessed
     * @throws IOException 
     */
    public void writeToXml(CarListings carAds, String carMake, 
            String carModel) throws IOException
    {        
        String currentDirectory = System.getProperty("user.dir");
        String fileSeparator = System.getProperty("file.separator");
        
        PrintWriter writeToFile = new PrintWriter(new BufferedWriter
        (new FileWriter(currentDirectory + fileSeparator + carMake + 
        carModel + ".xml")));
        
        writeToFile.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
        writeToFile.println("<project name=\"AutoTraderFraudCalculator\" "
                                + "default=\"default\" basedir=\".\">");
        
        writeToFile.println("<!--\n");
        writeToFile.println("Number of Listings: " + carAds.getSize());
        
        for (int i = 0; i < carAds.getSize(); i++)
        {
            writeToFile.println((i + 1) + " URL:=" + carAds.getAd(i).getUrl());
            writeToFile.println((i + 1) + 
                    " Title:=" + carAds.getAd(i).getTitle());
            
            for (int j = 0; j < carAds.getAd(i).getSpecs().size(); j++)
            {
                writeToFile.println
                ((i + 1) + " " + 
                        carAds.getAd(i).getSpecs().entrySet().toArray()[j]);
            }
        }
        writeToFile.println("-->");
        writeToFile.println("</project>");
        writeToFile.close();
    }
    
    /**
     * Retrieve the minimum price of all the listings of a particular car make 
     * and car model (the one that <code>downloadCarListings()</code> was called
     * with by default).
     * 
     * @return The minimum price of all the listings of the car make and model.
     */
    public int getMinPrice()
    {
        return minPrice;
    }
    
    /**
     * Retrieve the average price of all the listings of a particular car make 
     * and car model (the one that <code>downloadCarListings()</code> was called
     * with by default).
     * 
     * @return The average price of all the listings of the car make and model.
     */
    public int getAvgPrice()
    {
        return (int)(avgPrice + 0.5);
    }
    
    /**
     * Retrieve the maximum price of all the listings of a particular car make 
     * and car model (the one that <code>downloadCarListings()</code> was called
     * with by default).
     * 
     * @return The maximum price of all the listings of the car make and model.
     */
    public int getMaxPrice()
    {
        return maxPrice;
    }
    
    /**
     * Retrieve the minimum mileage of all the listings of a particular car make 
     * and car model (the one that <code>downloadCarListings()</code> was called
     * with by default).
     * 
     * @return The minimum mileage of all the listings of the car make and model.
     */
    public int getMinMileage()
    {
        return minMileage;
    }
    
    /**
     * Retrieve the average mileage of all the listings of a particular car make 
     * and car model (the one that <code>downloadCarListings()</code> was called
     * with by default).
     * 
     * @return The average mileage of all the listings of the car make and model.
     */
    public int getAvgMileage()
    {
        return (int)(avgMileage + 0.5);
    }
    
    /**
     * Retrieve the maximum mileage of all the listings of a particular car make 
     * and car model (the one that <code>downloadCarListings()</code> was called
     * with by default).
     * 
     * @return The maximum mileage of all the listings of the car make and model.
     */
    public int getMaxMileage()
    {
        return maxMileage;
    }
    
    /**
     * Set the minimum price of all the listings of a particular car make 
     * and car model.
     * 
     * @param minPriced The minimum price to set it to.
     */
    public void setMinPrice(int minPriced)
    {
        minPrice = minPriced;
    }
    
    /**
     * Set the average price of all the listings of a particular car make 
     * and car model.
     * 
     * @param avgPriced The average price to set it to.
     */
    public void setAvgPrice(int avgPriced)
    {
        avgPrice = avgPriced;
    }
    
    /**
     * Set the maximum price of all the listings of a particular car make 
     * and car model.
     * 
     * @param maxPriced The maximum price to set it to.
     */
    public void setMaxPrice(int maxPriced)
    {
        maxPrice = maxPriced;
    }
    
    /**
     * Set the minimum mileage of all the listings of a particular car make 
     * and car model.
     * 
     * @param minPriced The minimum mileage to set it to.
     */
    public void setMinMileage(int minMileaged)
    {
        minMileage = minMileaged;
    }
    
    /**
     * Set the average mileage of all the listings of a particular car make 
     * and car model.
     * 
     * @param avgPriced The average mileage to set it to.
     */
    public void setAvgMileage(int avgMileaged)
    {
        avgMileage = avgMileaged;
    }
    
    /**
     * Set the maximum mileage of all the listings of a particular car make 
     * and car model.
     * 
     * @param maxPriced The maximum mileage to set it to.
     */
    public void setMaxMileage(int maxMileaged)
    {
        maxMileage = maxMileaged;
    }
}